using System;
using System.Collections.Generic;
using System.Linq;


namespace Utilities
{
  public interface IActivable
  {
    bool IsActive { get; set; }
  }

  public interface IReference
  {
    void Delete();
  }
  public struct UnitReference : IReference
  {
    public void Delete() { }
  }

  public class Chunk<T> where T : IActivable
  {
    public Chunk(int index) 
    { 
      Items = new T[StructAllocator<T>.ChunkSize]; 
      NumFreeItems = StructAllocator<T>.ChunkSize;
      Index = index;
    }
    public int Index;
    public T[] Items;
    public int NumFreeItems;
  }

  public struct Reference : IEquatable<Reference>
  {
    public int ChunkIndex; public int ItemIndex;

    public bool Equals(Reference other)
    {
      return ChunkIndex == other.ChunkIndex && ItemIndex == other.ItemIndex;
    }
  }

  public static class StructAllocatorManager
  {
    static internal List<Action> struct_allocators_reset = new List<Action>();
    static public void Reset() { struct_allocators_reset.ForEach(a => a()); }
  }

  public static class StructAllocator<T> where T : IActivable
  {
    static public readonly int ChunkSize = 25;
    static public void Reset() { Chunks = Enumerable.Range(0, 10).Select(i => new Chunk<T>(i)).ToList(); }

    static private Func<bool> start = () => { StructAllocatorManager.struct_allocators_reset.Add(StructAllocator<T>.Reset); return true; };
    static private bool started = start();

    static public List<Chunk<T>> Chunks = Enumerable.Range(0,10).Select(i => new Chunk<T>(i)).ToList();

    static private List<Action<Reference>> OnEnable = new List<Action<Reference>>();
    static private List<Action<Reference>> OnDisable = new List<Action<Reference>>();
    public struct LinkTrace {
      public Action<Reference> enable;
      public Action<Reference> disable;
    }
    static public LinkTrace Link(Action<Reference> on_enable, Action<Reference> on_disable)
    {
      OnEnable.Add(on_enable);
      OnDisable.Add(on_disable);
      return new LinkTrace() { enable = on_enable, disable = on_disable };
    }
    static public void Unlink(LinkTrace link)
    {
      OnEnable.Remove(link.enable);
      OnDisable.Remove(link.disable);
    }

    static public Reference New()
    {
      var chunk = Chunks.FirstOrDefault(c => c.NumFreeItems > 0);
      if (chunk == null)
      {
        Chunks.Add(new Chunk<T>(Chunks.Count));
        return New();
      }
      chunk.NumFreeItems--;
      var item_index = chunk.Items.Select((v, i) => new { v, i }).First(vi => vi.v.IsActive == false).i;
      chunk.Items[item_index].IsActive = true;
      var chunk_index = chunk.Index;
      var res = new Reference(){ChunkIndex = chunk_index, ItemIndex = item_index};
      OnEnable.ForEach(a => a(res));
      return res;
    }

    static public int UsedItems
    {
      get { return Chunks.Sum(c => c.Items.Count(i => i.IsActive)); }
    }

    static public bool FreeItemsOk
    {
      get { return Chunks.All(c => c.Items.Count(i => i.IsActive == false) == c.NumFreeItems); }
    }

    static public void Delete(Reference item)
    {
      var chunk = Chunks[item.ChunkIndex];
      chunk.NumFreeItems++;
      chunk.Items[item.ItemIndex].IsActive = false;
      OnDisable.ForEach(a => a(item));
    }

    static public void Enable(Reference item)
    {
      while (Chunks.Count <= item.ChunkIndex) Chunks.Add(new Chunk<T>(Chunks.Count));
      Chunks[item.ChunkIndex].Items[item.ItemIndex].IsActive = true;
    }

    static public void Disable(Reference item)
    {
      Chunks[item.ChunkIndex].Items[item.ItemIndex].IsActive = false;
    }
  }
}
